/*
 * author	: calvin
 * email	: calvinwilliams@163.com
 *
 * Licensed under the LGPL v2.1, see the file LICENSE in base directory.
 */

#include "cdbc.h"

#if defined(__linux__)
#include <dlfcn.h>
#elif defined(_WIN32)
#include <windows.h>
#include <string.h>
#endif

static char	_cdbc_sg_db_type_name[][10+1] = { "" , "MySQL" , "PostgreSQL" , "Sqlite" , "Oracle" } ;
static char	_cdbc_sg_db_lowercase_type_name[][10+1] = { "" , "mysql" , "postgresql" , "sqlite" , "oracle" } ;

static TLS int	_cdbc_sg_last_errno = 0 ;
static TLS int	_cdbc_sg_last_native_errno = 0 ;
static TLS char	_cdbc_sg_last_native_error[ 256 ] = "" ;
static TLS char	_cdbc_sg_last_sqlstate[ 6 ] = "" ;

#define CDBC_DATABASE_TYPE_MYSQL_ID		1
#define CDBC_DATABASE_TYPE_POSTGRESQL_ID	2
#define CDBC_DATABASE_TYPE_ORACLE_ID		4
#define CDBC_DATABASE_TYPE_SQLITE_ID		3

struct DatabaseDriver
{
	char				*driver_name ;
	
	void				*so_handler ;
	
	funcInitDriverLibrary		*pfuncInitDriverLibrary ;
	funcEndDriverLibrary		*pfuncEndDriverLibrary ;
	funcConnectToDatabase		*pfuncConnectToDatabase ;
	funcDisconnectFromDatabase	*pfuncDisconnectFromDatabase ;
	funcExecuteSql			*pfuncExecuteSql ;
	funcAutoCommitTransaction	*pfuncAutoCommitTransaction ;
	funcBeginTransaction		*pfuncBeginTransaction ;
	funcCommitTransaction		*pfuncCommitTransaction ;
	funcRollbackTransaction		*pfuncRollbackTransaction ;
} ;

static TLS struct DatabaseDriverManager
{
	int			dummy ;
	struct DatabaseDriver	db_driver[ sizeof(_cdbc_sg_db_type_name)/sizeof(_cdbc_sg_db_type_name[0]) ] ;
} _cdbc_g_dbdrivermanager = { 0 } ;

struct DatabaseDriver *DBCGetDatabaseDriver( char *db_type_name )
{
	int			db_id ;
	struct DatabaseDriver	*p_db_driver = NULL ;
	char			*so_file_extname = NULL ;
	char			pathfilename[ PATH_MAX ] ;
	int			nret = 0 ;
	
	_cdbc_sg_last_errno = 0 ;
	
	for( db_id = 1 ; db_id < sizeof(_cdbc_sg_db_type_name)/sizeof(_cdbc_sg_db_type_name[0]) ; db_id++ )
	{
		if( STRICMP( db_type_name , == , _cdbc_sg_db_type_name[db_id] ) )
		{
			p_db_driver = _cdbc_g_dbdrivermanager.db_driver + db_id ;
			break;
		}
	}
	if( db_id >= sizeof(_cdbc_sg_db_type_name)/sizeof(_cdbc_sg_db_type_name[0]) )
		return NULL;
	
	if( p_db_driver->so_handler )
		return p_db_driver;
	
	p_db_driver->driver_name = _cdbc_sg_db_type_name[db_id] ;
	
#if ( defined __linux ) || ( defined __unix )
	so_file_extname = "so" ;
#elif ( defined _WIN32 )
	so_file_extname = "dll" ;
#endif
	
	do
	{
		memset( pathfilename , 0x00 , sizeof(pathfilename) );
		snprintf( pathfilename , sizeof(pathfilename)-1 , "dbdriver_%s.%s" , _cdbc_sg_db_lowercase_type_name[db_id] , so_file_extname );
#if ( defined __linux ) || ( defined __unix )
		p_db_driver->so_handler = dlopen( pathfilename , RTLD_LAZY|RTLD_GLOBAL ) ;
#elif ( defined _WIN32 )
		p_db_driver->so_handler = LoadLibrary( pathfilename ) ;
#endif
		if( p_db_driver->so_handler )
			break;
		
		if( getenv("ZLANG_HOME") )
		{
			memset( pathfilename , 0x00 , sizeof(pathfilename) );
			snprintf( pathfilename , sizeof(pathfilename)-1 , "%s/bin/dbdriver_%s.%s" , getenv("ZLANG_HOME") , _cdbc_sg_db_lowercase_type_name[db_id] , so_file_extname );
#if ( defined __linux ) || ( defined __unix )
			p_db_driver->so_handler = dlopen( pathfilename , RTLD_LAZY|RTLD_GLOBAL ) ;
#elif ( defined _WIN32 )
			p_db_driver->so_handler = LoadLibrary( pathfilename ) ;
#endif
			if( p_db_driver->so_handler )
				break;
		}

		memset( pathfilename , 0x00 , sizeof(pathfilename) );
		snprintf( pathfilename , sizeof(pathfilename)-1 , "%s/lib/dbdriver_%s.%s" , getenv("HOME") , _cdbc_sg_db_lowercase_type_name[db_id] , so_file_extname );
#if ( defined __linux ) || ( defined __unix )
		p_db_driver->so_handler = dlopen( pathfilename , RTLD_LAZY|RTLD_GLOBAL ) ;
#elif ( defined _WIN32 )
		p_db_driver->so_handler = LoadLibrary( pathfilename ) ;
#endif
		if( p_db_driver->so_handler )
			break;
		
		memset( pathfilename , 0x00 , sizeof(pathfilename) );
		snprintf( pathfilename , sizeof(pathfilename)-1 , "/usr/lib/dbdriver_%s.%s" , _cdbc_sg_db_lowercase_type_name[db_id] , so_file_extname );
#if ( defined __linux ) || ( defined __unix )
		p_db_driver->so_handler = dlopen( pathfilename , RTLD_LAZY|RTLD_GLOBAL ) ;
#elif ( defined _WIN32 )
		p_db_driver->so_handler = LoadLibrary( pathfilename ) ;
#endif
		if( p_db_driver->so_handler )
			break;
		
		memset( pathfilename , 0x00 , sizeof(pathfilename) );
		snprintf( pathfilename , sizeof(pathfilename)-1 , "/lib/dbdriver_%s.%s" , _cdbc_sg_db_lowercase_type_name[db_id] , so_file_extname );
#if ( defined __linux ) || ( defined __unix )
		p_db_driver->so_handler = dlopen( pathfilename , RTLD_LAZY|RTLD_GLOBAL ) ;
#elif ( defined _WIN32 )
		p_db_driver->so_handler = LoadLibrary( pathfilename ) ;
#endif
		if( p_db_driver->so_handler )
			break;
		
	}
	while(0);
	if( p_db_driver->so_handler == NULL )
	{
		_cdbc_sg_last_errno = CDBC_ERROR_FILE_NOT_FOUND ;
		return NULL;
	}
	
#if ( defined __linux ) || ( defined __unix )
	p_db_driver->pfuncInitDriverLibrary = (funcInitDriverLibrary *)dlsym( p_db_driver->so_handler , CDBC_FUNCNAME_INITDRIVERLIBRARY ) ;
#elif ( defined _WIN32 )
	p_db_driver->pfuncInitDriverLibrary = (funcInitDriverLibrary *)GetProcAddress( p_db_driver->so_handler , CDBC_FUNCNAME_INITDRIVERLIBRARY ) ;
#endif
	if( p_db_driver->pfuncInitDriverLibrary )
	{
		nret = p_db_driver->pfuncInitDriverLibrary() ;
		if( nret )
		{
			_cdbc_sg_last_errno = CDBC_ERROR_INIT_FAILED_IN_DRIVER ;
#if ( defined __linux ) || ( defined __unix )
			dlclose( p_db_driver->so_handler ); p_db_driver->so_handler = NULL ;
#elif ( defined _WIN32 )
			FreeLibrary( p_db_driver->so_handler ); p_db_driver->so_handler = NULL ;
#endif
			return NULL;
		}
	}
	
#if ( defined __linux ) || ( defined __unix )
	p_db_driver->pfuncEndDriverLibrary = (funcEndDriverLibrary *)dlsym( p_db_driver->so_handler , CDBC_FUNCNAME_ENDDRIVERLIBRARY ) ;
#elif ( defined _WIN32 )
	p_db_driver->pfuncEndDriverLibrary = (funcEndDriverLibrary *)GetProcAddress( p_db_driver->so_handler , CDBC_FUNCNAME_ENDDRIVERLIBRARY ) ;
#endif
	
#if ( defined __linux ) || ( defined __unix )
	p_db_driver->pfuncConnectToDatabase = (funcConnectToDatabase *)dlsym( p_db_driver->so_handler , CDBC_FUNCNAME_CONNECTTODATABASE ) ;
#elif ( defined _WIN32 )
	p_db_driver->pfuncConnectToDatabase = (funcConnectToDatabase *)GetProcAddress( p_db_driver->so_handler , CDBC_FUNCNAME_CONNECTTODATABASE ) ;
#endif
	if( p_db_driver->pfuncConnectToDatabase == NULL )
	{
		_cdbc_sg_last_errno = CDBC_ERROR_NO_FUNCTION_IN_DRIVER ;
#if ( defined __linux ) || ( defined __unix )
		dlclose( p_db_driver->so_handler ); p_db_driver->so_handler = NULL ;
#elif ( defined _WIN32 )
		FreeLibrary( p_db_driver->so_handler ); p_db_driver->so_handler = NULL ;
#endif
		return NULL;
	}
	
#if ( defined __linux ) || ( defined __unix )
	p_db_driver->pfuncDisconnectFromDatabase = (funcDisconnectFromDatabase *)dlsym( p_db_driver->so_handler , CDBC_FUNCNAME_DISCONNECTFROMDATABASE ) ;
#elif ( defined _WIN32 )
	p_db_driver->pfuncDisconnectFromDatabase = (funcDisconnectFromDatabase *)GetProcAddress( p_db_driver->so_handler , CDBC_FUNCNAME_DISCONNECTFROMDATABASE ) ;
#endif
	if( p_db_driver->pfuncDisconnectFromDatabase == NULL )
	{
		_cdbc_sg_last_errno = CDBC_ERROR_NO_FUNCTION_IN_DRIVER ;
#if ( defined __linux ) || ( defined __unix )
		dlclose( p_db_driver->so_handler ); p_db_driver->so_handler = NULL ;
#elif ( defined _WIN32 )
		FreeLibrary( p_db_driver->so_handler ); p_db_driver->so_handler = NULL ;
#endif
		return NULL;
	}
	
#if ( defined __linux ) || ( defined __unix )
	p_db_driver->pfuncExecuteSql = (funcExecuteSql *)dlsym( p_db_driver->so_handler , CDBC_FUNCNAME_EXECUTESQL ) ;
#elif ( defined _WIN32 )
	p_db_driver->pfuncExecuteSql = (funcExecuteSql *)GetProcAddress( p_db_driver->so_handler , CDBC_FUNCNAME_EXECUTESQL ) ;
#endif
	if( p_db_driver->pfuncExecuteSql == NULL )
	{
		_cdbc_sg_last_errno = CDBC_ERROR_NO_FUNCTION_IN_DRIVER ;
#if ( defined __linux ) || ( defined __unix )
		dlclose( p_db_driver->so_handler ); p_db_driver->so_handler = NULL ;
#elif ( defined _WIN32 )
		FreeLibrary( p_db_driver->so_handler ); p_db_driver->so_handler = NULL ;
#endif
		return NULL;
	}
	
#if ( defined __linux ) || ( defined __unix )
	p_db_driver->pfuncAutoCommitTransaction = (funcAutoCommitTransaction *)dlsym( p_db_driver->so_handler , CDBC_FUNCNAME_AUTOCOMMITTRANSACTION ) ;
	p_db_driver->pfuncBeginTransaction = (funcBeginTransaction *)dlsym( p_db_driver->so_handler , CDBC_FUNCNAME_BEGINTRANSACTION ) ;
	p_db_driver->pfuncCommitTransaction = (funcCommitTransaction *)dlsym( p_db_driver->so_handler , CDBC_FUNCNAME_COMMITTRANSACTION ) ;
	p_db_driver->pfuncRollbackTransaction = (funcRollbackTransaction *)dlsym( p_db_driver->so_handler , CDBC_FUNCNAME_ROLLBACKTRANSACTION ) ;
#elif ( defined _WIN32 )
	p_db_driver->pfuncAutoCommitTransaction = (funcAutoCommitTransaction *)GetProcAddress( p_db_driver->so_handler , CDBC_FUNCNAME_AUTOCOMMITTRANSACTION ) ;
	p_db_driver->pfuncBeginTransaction = (funcBeginTransaction *)GetProcAddress( p_db_driver->so_handler , CDBC_FUNCNAME_BEGINTRANSACTION ) ;
	p_db_driver->pfuncCommitTransaction = (funcCommitTransaction *)GetProcAddress( p_db_driver->so_handler , CDBC_FUNCNAME_COMMITTRANSACTION ) ;
	p_db_driver->pfuncRollbackTransaction = (funcRollbackTransaction *)GetProcAddress( p_db_driver->so_handler , CDBC_FUNCNAME_ROLLBACKTRANSACTION ) ;
#endif
	
	return p_db_driver;
}

char *DBCGetDatabaseDriverName( struct DatabaseDriver *db_driver )
{
	return db_driver->driver_name;
}

void DBCUnloadAllDatabaseDrivers()
{
	int			db_id ;
	struct DatabaseDriver	*p_db_driver = NULL ;
	
	_cdbc_sg_last_errno = 0 ;
	
	for( db_id = 1 ; db_id < sizeof(_cdbc_sg_db_type_name)/sizeof(_cdbc_sg_db_type_name[0]) ; db_id++ )
	{
		p_db_driver = _cdbc_g_dbdrivermanager.db_driver + db_id ;
		if( p_db_driver->so_handler && p_db_driver->pfuncEndDriverLibrary )
		{
			p_db_driver->pfuncEndDriverLibrary();
		}
	}
	
	return;
}

struct DatabaseConnection *DBCConnectToDatabase( struct DatabaseDriver *db_driver , char *db_host , int db_port , char *db_user , char *db_pass , char *db_name )
{
	_cdbc_sg_last_errno = 0 ;
	
	if( db_driver == NULL )
	{
		_cdbc_sg_last_errno = CDBC_ERROR_PARAMETER ;
		return NULL;
	}
	
	if( db_driver->pfuncConnectToDatabase == NULL )
	{
		_cdbc_sg_last_errno = CDBC_ERROR_NO_FUNCTION_IN_DRIVER ;
		return NULL;
	}
	
	return db_driver->pfuncConnectToDatabase( db_host , db_port , db_user , db_pass , db_name );
}

void DBCDisconnecFromDatabase( struct DatabaseDriver *db_driver , struct DatabaseConnection **db_conn )
{
	_cdbc_sg_last_errno = 0 ;
	
	if( db_driver == NULL )
	{
		_cdbc_sg_last_errno = CDBC_ERROR_PARAMETER ;
		return;
	}
	
	if( db_driver->pfuncDisconnectFromDatabase == NULL )
	{
		_cdbc_sg_last_errno = CDBC_ERROR_NO_FUNCTION_IN_DRIVER ;
		return;
	}
	
	db_driver->pfuncDisconnectFromDatabase( db_conn );
	
	return;
}

void DBCExecuteSql( struct DatabaseDriver *db_driver , struct DatabaseConnection *db_conn , char *sql , struct FieldBind *binds_array , int binds_array_length , int *row_count , int *col_count , struct FieldInfo **query_field_set , char ***query_result_set , int *affected_count )
{
	_cdbc_sg_last_errno = 0 ;
	
	if( db_driver == NULL )
	{
		_cdbc_sg_last_errno = CDBC_ERROR_PARAMETER ;
		return;
	}
	
	if( db_driver->pfuncExecuteSql == NULL )
	{
		_cdbc_sg_last_errno = CDBC_ERROR_NO_FUNCTION_IN_DRIVER ;
		return;
	}
	
	db_driver->pfuncExecuteSql( db_conn , sql , binds_array , binds_array_length , row_count , col_count , query_field_set , query_result_set , affected_count );
	
	return;
}

void DBCFreeSqlResult( struct FieldInfo **query_field_set , char ***query_result_set )
{
	int			col_no ;
	int			col_count = 0 ;
	int			row_count = 0 ;
	struct FieldInfo	*p_field_info = NULL ;
	char			**p = NULL ;
	int			result_no ;
	int			result_count ;
	
	if( query_field_set && (*query_field_set) )
	{
		for( col_no = 0 , p_field_info = (*query_field_set) , col_count = 0 ; p_field_info->field_name ; col_no++ , p_field_info++ , col_count++ )
		{
			if( p_field_info->field_name == NULL )
				break;
			
			free( p_field_info->field_name );
		}
		
		row_count = p_field_info->field_length ;
		
		free( (*query_field_set) ); (*query_field_set) = NULL ;
	}
	
	if( query_result_set && (*query_result_set) && col_count > 0 && row_count > 0 )
	{
		result_count = row_count * col_count ;
		for( result_no = 0 , p = (*query_result_set) ; result_no < result_count ; result_no++ , p++ )
		{
			if( (*p) )
				free( (*p) );
		}
		
		free( (*query_result_set) ); (*query_result_set) = NULL ;
	}
	
	return;
}

void DBCAutoCommitTransaction( struct DatabaseDriver *db_driver , struct DatabaseConnection *db_conn , unsigned char enable_autocommit )
{
	_cdbc_sg_last_errno = 0 ;
	
	if( db_driver == NULL || db_driver->pfuncAutoCommitTransaction == NULL )
		return;
	
	db_driver->pfuncAutoCommitTransaction( db_conn , (enable_autocommit?1:0) );
	
	return;
}

void DBCBeginTransaction( struct DatabaseDriver *db_driver , struct DatabaseConnection *db_conn )
{
	_cdbc_sg_last_errno = 0 ;
	
	if( db_driver == NULL )
	{
		_cdbc_sg_last_errno = CDBC_ERROR_PARAMETER ;
		return;
	}
	
	if( db_driver->pfuncBeginTransaction == NULL )
		return;
	
	db_driver->pfuncBeginTransaction( db_conn );
	
	return;
}

void DBCCommitTransaction( struct DatabaseDriver *db_driver , struct DatabaseConnection *db_conn )
{
	_cdbc_sg_last_errno = 0 ;
	
	if( db_driver == NULL )
	{
		_cdbc_sg_last_errno = CDBC_ERROR_PARAMETER ;
		return;
	}
	
	if( db_driver->pfuncCommitTransaction == NULL )
		return;
	
	db_driver->pfuncCommitTransaction( db_conn );
	
	return;
}

void DBCRollbackTransaction( struct DatabaseDriver *db_driver , struct DatabaseConnection *db_conn )
{
	_cdbc_sg_last_errno = 0 ;
	
	if( db_driver == NULL )
	{
		_cdbc_sg_last_errno = CDBC_ERROR_PARAMETER ;
		return;
	}
	
	if( db_driver->pfuncRollbackTransaction == NULL )
		return;
	
	db_driver->pfuncRollbackTransaction( db_conn );
	
	return;
}

void DBCSetLastErrno( int last_errno )
{
	_cdbc_sg_last_errno = last_errno ;
	
	return;
}

int DBCGetLastErrno()
{
	return _cdbc_sg_last_errno;
}

void DBCSetLastNativeErrno( long last_native_errno )
{
	_cdbc_sg_last_native_errno = last_native_errno ;
	
	return;
}

long DBCGetLastNativeErrno()
{
	return _cdbc_sg_last_native_errno;
}

void DBCSetLastNativeError( char *error_str )
{
	memset( _cdbc_sg_last_native_error , 0x00 , sizeof(_cdbc_sg_last_native_error) );
	strncpy( _cdbc_sg_last_native_error , error_str , sizeof(_cdbc_sg_last_native_error)-1 );
	
	return;
}

char *DBCGetLastNativeError()
{
	return _cdbc_sg_last_native_error;
}

void DBCSetLastSqlState( char *sqlstate )
{
	memset( _cdbc_sg_last_sqlstate , 0x00 , sizeof(_cdbc_sg_last_sqlstate) );
	strncpy( _cdbc_sg_last_sqlstate , sqlstate , sizeof(_cdbc_sg_last_sqlstate)-1 );
	
	return;
}

char *DBCGetLastSqlState()
{
	return _cdbc_sg_last_sqlstate;
}

int InitResizableBuffer( struct ResizableBuffer *rb , size_t init_size , size_t increase_size )
{
	memset( rb , 0x00 , sizeof(struct ResizableBuffer) );
	
	rb->buf_base = (char*)malloc( init_size ) ;
	if( rb->buf_base == NULL )
	{
		free( rb );
		return -2;
	}
	memset( rb->buf_base , 0x00 , init_size );
	rb->buf_ptr = rb->buf_base ;
	rb->data_length = 0 ;
	rb->buf_size = (uint32_t)init_size ;
	
	rb->increase_size = increase_size ;
	
	return 0;
}

void CleanResizableBuffer( struct ResizableBuffer *rb )
{
	if( rb )
	{
		if( rb->buf_base )
		{
			free( rb->buf_base ); rb->buf_base = NULL ;
		}
	}
	
	return;
}

char *AppendResizableBuffer( struct ResizableBuffer *rb , char *data , uint32_t data_len , char **rebase )
{
	char		*buf_ptr = NULL ;
	
	if( rb->buf_base == NULL )
		return NULL;
	
	if( rb->data_length + data_len + 1 > rb->buf_size-1 )
	{
		uint32_t	new_buf_size ;
		char		*new_buf_base = NULL ;
		
		if( rb->data_length + data_len + 1 <= rb->buf_size-1 + rb->increase_size )
			new_buf_size = rb->buf_size + (uint32_t)(rb->increase_size) ;
		else
			new_buf_size = rb->data_length + data_len + 1 + 1 ;
		
		new_buf_base = (char*)realloc( rb->buf_base , new_buf_size ) ;
		if( new_buf_base == NULL )
			return NULL;
		rb->buf_ptr = new_buf_base + (rb->buf_ptr-rb->buf_base) ;
		rb->buf_base = new_buf_base ;
		memset( rb->buf_ptr , 0x00 , new_buf_size-rb->buf_size );
		rb->buf_size = new_buf_size ;
		
		if( rebase )
			(*rebase) = rb->buf_base ;
	}
	
	buf_ptr = rb->buf_ptr ;
	
	memcpy( rb->buf_ptr , data , data_len );
	rb->buf_ptr[data_len] = '\0' ;
	rb->data_length += data_len + 1 ;
	rb->buf_ptr += data_len + 1 ;
	
	return buf_ptr;
}

#if ( defined _WIN32 )
char *_cdbc_strndup(const char *s, size_t n)
{
  char *result;
  size_t len = strlen (s);

  if (n < len)
    len = n;

  result = (char *)malloc(len + 1);
  if (!result)
    return 0;

  result[len] = '\0';
  return (char *) memcpy (result, s, len);
}

char *_cdbc_stristr(const char* str, const char* substr) {
    char* str_lower = STRDUP(str);
    char* substr_lower = STRDUP(substr);
    char* ptr;
 
    // Convert str and substr to lowercase.
    for (ptr = str_lower; *ptr != '\0'; ptr++) {
        *ptr = tolower((unsigned char)*ptr);
    }
    for (ptr = substr_lower; *ptr != '\0'; ptr++) {
        *ptr = tolower((unsigned char)*ptr);
    }
 
    // Find the first occurrence of substr in str using strstr.
    char* found = strstr(str_lower, substr_lower);
 
    // Free the lowercase strings and return the original occurrence.
    free(str_lower);
    free(substr_lower);
    return found ? (char*)str + (found - str_lower) : NULL;
}
#endif

#if defined(_WIN32)
#define ALT_E          0x01
#define ALT_O          0x02
 //#define LEGAL_ALT(x)       { if (alt_format & ~(x)) return (0); }
#define LEGAL_ALT(x)       { ; }
#define TM_YEAR_BASE   (1900)

static int _conv_num(const char **buf, int *dest, int llim, int ulim)
{
        int result = 0;

        /* The limit also determines the number of valid digits. */
        int rulim = ulim;

        if (**buf < '0' || **buf > '9')
                return (0);

        do {
                result *= 10;
                result += *(*buf)++ - '0';
                rulim /= 10;
        } while ((result * 10 <= ulim) && rulim && **buf >= '0' && **buf <= '9');

        if (result < llim || result > ulim)
                return (0);

        *dest = result;
        return (1);
}

static int _strncasecmp(char *s1, char *s2, size_t n)
{
        if (n == 0)
                return 0;

        while (n-- != 0 && tolower(*s1) == tolower(*s2))
        {
                if (n == 0 || *s1 == '\0' || *s2 == '\0')
                        break;
                s1++;
                s2++;
        }

        return tolower(*(unsigned char *) s1) - tolower(*(unsigned char *) s2);
}

static const char *day[7] = {
        "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday",
        "Friday", "Saturday"
};
static const char *abday[7] = {
        "Sun","Mon","Tue","Wed","Thu","Fri","Sat"
};
static const char *mon[12] = {
        "January", "February", "March", "April", "May", "June", "July",
        "August", "September", "October", "November", "December"
};
static const char *abmon[12] = {
        "Jan", "Feb", "Mar", "Apr", "May", "Jun",
        "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
static const char *am_pm[2] = {
        "AM", "PM"
};

char *strptime(const char *buf, const char *fmt, struct tm *tm)
{
        char c;
        const char *bp;
        size_t len = 0;
        int alt_format, i, split_year = 0;

        bp = buf;

        while ((c = *fmt) != '\0') {
                /* Clear `alternate' modifier prior to new conversion. */
                alt_format = 0;

                /* Eat up white-space. */
                if (isspace(c)) {
                        while (isspace(*bp))
                                bp++;

                        fmt++;
                        continue;
                }

                if ((c = *fmt++) != '%')
                        goto literal;


        again:        switch (c = *fmt++) {
        case '%': /* "%%" is converted to "%". */
                literal:
                        if (c != *bp++)
                                return (0);
                break;

                /*
                * "Alternative" modifiers. Just set the appropriate flag
                * and start over again.
                */
        case 'E': /* "%E?" alternative conversion modifier. */
                LEGAL_ALT(0);
                alt_format |= ALT_E;
                goto again;

        case 'O': /* "%O?" alternative conversion modifier. */
                LEGAL_ALT(0);
                alt_format |= ALT_O;
                goto again;

                /*
                * "Complex" conversion rules, implemented through recursion.
                */
        case 'c': /* Date and time, using the locale's format. */
                LEGAL_ALT(ALT_E);
                if (!(bp = strptime(bp, "%x %X", tm)))
                        return (0);
                break;

        case 'D': /* The date as "%m/%d/%y". */
                LEGAL_ALT(0);
                if (!(bp = strptime(bp, "%m/%d/%y", tm)))
                        return (0);
                break;

        case 'R': /* The time as "%H:%M". */
                LEGAL_ALT(0);
                if (!(bp = strptime(bp, "%H:%M", tm)))
                        return (0);
                break;

        case 'r': /* The time in 12-hour clock representation. */
                LEGAL_ALT(0);
                if (!(bp = strptime(bp, "%I:%M:%S %p", tm)))
                        return (0);
                break;

        case 'T': /* The time as "%H:%M:%S". */
                LEGAL_ALT(0);
                if (!(bp = strptime(bp, "%H:%M:%S", tm)))
                        return (0);
                break;

        case 'X': /* The time, using the locale's format. */
                LEGAL_ALT(ALT_E);
                if (!(bp = strptime(bp, "%H:%M:%S", tm)))
                        return (0);
                break;

        case 'x': /* The date, using the locale's format. */
                LEGAL_ALT(ALT_E);
                if (!(bp = strptime(bp, "%m/%d/%y", tm)))
                        return (0);
                break;

                /*
                * "Elementary" conversion rules.
                */
        case 'A': /* The day of week, using the locale's form. */
        case 'a':
                LEGAL_ALT(0);
                for (i = 0; i < 7; i++) {
                        /* Full name. */
                        len = strlen(day[i]);
                        if (_strncasecmp((char *)(day[i]), (char *)bp, len) == 0)
                                break;

                        /* Abbreviated name. */
                        len = strlen(abday[i]);
                        if (_strncasecmp((char *)(abday[i]), (char *)bp, len) == 0)
                                break;
                }

                /* Nothing matched. */
                if (i == 7)
                        return (0);

                tm->tm_wday = i;
                bp += len;
                break;

        case 'B': /* The month, using the locale's form. */
        case 'b':
        case 'h':
                LEGAL_ALT(0);
                for (i = 0; i < 12; i++) {
                        /* Full name. */
                        len = strlen(mon[i]);
                        if (_strncasecmp((char *)(mon[i]), (char *)bp, len) == 0)
                                break;

                        /* Abbreviated name. */
                        len = strlen(abmon[i]);
                        if (_strncasecmp((char *)(abmon[i]),(char *) bp, len) == 0)
                                break;
                }

                /* Nothing matched. */
                if (i == 12)
                        return (0);

                tm->tm_mon = i;
                bp += len;
                break;

        case 'C': /* The century number. */
                LEGAL_ALT(ALT_E);
                if (!(_conv_num(&bp, &i, 0, 99)))
                        return (0);

                if (split_year) {
                        tm->tm_year = (tm->tm_year % 100) + (i * 100);
                } else {
                        tm->tm_year = i * 100;
                        split_year = 1;
                }
                break;

        case 'd': /* The day of month. */
        case 'e':
                LEGAL_ALT(ALT_O);
                if (!(_conv_num(&bp, &tm->tm_mday, 1, 31)))
                        return (0);
                break;

        case 'k': /* The hour (24-hour clock representation). */
                LEGAL_ALT(0);
                /* FALLTHROUGH */
        case 'H':
                LEGAL_ALT(ALT_O);
                if (!(_conv_num(&bp, &tm->tm_hour, 0, 23)))
                        return (0);
                break;

        case 'l': /* The hour (12-hour clock representation). */
                LEGAL_ALT(0);
                /* FALLTHROUGH */
        case 'I':
                LEGAL_ALT(ALT_O);
                if (!(_conv_num(&bp, &tm->tm_hour, 1, 12)))
                        return (0);
                if (tm->tm_hour == 12)
                        tm->tm_hour = 0;
                break;

        case 'j': /* The day of year. */
                LEGAL_ALT(0);
                if (!(_conv_num(&bp, &i, 1, 366)))
                        return (0);
                tm->tm_yday = i - 1;
                break;

        case 'M': /* The minute. */
                LEGAL_ALT(ALT_O);
                if (!(_conv_num(&bp, &tm->tm_min, 0, 59)))
                        return (0);
                break;

        case 'm': /* The month. */
                LEGAL_ALT(ALT_O);
                if (!(_conv_num(&bp, &i, 1, 12)))
                        return (0);
                tm->tm_mon = i - 1;
                break;

                //       case 'p': /* The locale's equivalent of AM/PM. */
                //            LEGAL_ALT(0);
                //            /* AM? */
                //            if (strcasecmp(am_pm[0], bp) == 0) {
                //                 if (tm->tm_hour > 11)
                //                     return (0);
                //
                //                 bp += strlen(am_pm[0]);
                //                 break;
                //            }
                //            /* PM? */
                //            else if (strcasecmp(am_pm[1], bp) == 0) {
                //                 if (tm->tm_hour > 11)
                //                     return (0);
                //
                //                 tm->tm_hour += 12;
                //                 bp += strlen(am_pm[1]);
                //                 break;
                //            }
                //
                //            /* Nothing matched. */
                //            return (0);

        case 'S': /* The seconds. */
                LEGAL_ALT(ALT_O);
                if (!(_conv_num(&bp, &tm->tm_sec, 0, 61)))
                        return (0);
                break;

        case 'U': /* The week of year, beginning on sunday. */
        case 'W': /* The week of year, beginning on monday. */
                LEGAL_ALT(ALT_O);
                /*
                * XXX This is bogus, as we can not assume any valid
                * information present in the tm structure at this
                * point to calculate a real value, so just check the
                * range for now.
                */
                if (!(_conv_num(&bp, &i, 0, 53)))
                        return (0);
                break;

        case 'w': /* The day of week, beginning on sunday. */
                LEGAL_ALT(ALT_O);
                if (!(_conv_num(&bp, &tm->tm_wday, 0, 6)))
                        return (0);
                break;

        case 'Y': /* The year. */
                LEGAL_ALT(ALT_E);
                if (!(_conv_num(&bp, &i, 0, 9999)))
                        return (0);

                tm->tm_year = i - TM_YEAR_BASE;
                break;

        case 'y': /* The year within 100 years of the epoch. */
                LEGAL_ALT(ALT_E | ALT_O);
                if (!(_conv_num(&bp, &i, 0, 99)))
                        return (0);

                if (split_year) {
                        tm->tm_year = ((tm->tm_year / 100) * 100) + i;
                        break;
                }
                split_year = 1;
                if (i <= 68)
                        tm->tm_year = i + 2000 - TM_YEAR_BASE;
                else
                        tm->tm_year = i + 1900 - TM_YEAR_BASE;
                break;

                /*
                * Miscellaneous conversions.
                */
        case 'n': /* Any kind of white-space. */
        case 't':
                LEGAL_ALT(0);
                while (isspace(*bp))
                        bp++;
                break;


        default: /* Unknown/unsupported conversion. */
                return (0);
        }


        }

        /* LINTED functional specification */
        return ((char *)bp);
}
#endif
